/* EasyWay Game Engine
 * Copyright (C) 2006 Daniele Paggi.
 *  
 * Written by: 2006 Daniele Paggi<dshnt@hotmail.com>
 *   
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.easyway.objects.sprites2D;

import java.io.Serializable;

import org.easyway.annotations.Optimized;
import org.easyway.annotations.editor.Creator;
import org.easyway.annotations.editor.Info;
import org.easyway.annotations.editor.Method;
import org.easyway.annotations.editor.UEditor;
import org.easyway.annotations.editor.X;
import org.easyway.annotations.editor.Y;
import org.easyway.interfaces.base.ITexture;
import org.easyway.interfaces.extended.IDrawing;
import org.easyway.interfaces.extended.ILayerID;
import org.easyway.objects.Camera;
import org.easyway.objects.Plain2D;
import org.easyway.objects.StateObject;
import org.easyway.objects.animo.Animo;
import org.easyway.system.StaticRef;
import org.easyway.system.state.OpenGLState;
import org.lwjgl.opengl.GL11;

/**
 * A SimpleSprite is an Object that can moved and drawen.<br>
 * It can have a Texture applied on or an Animation.<br>
 * It can't have any collisions: for this you must use SimpleSpriteColl class or
 * SpriteClass.<br>
 * For a complex managment of Sprite I guess you to use the Sprite class.<br>
 * 
 * @see SimpleSpriteColl
 * @see Sprite
 * @see SpriteColl
 * @author Daniele Paggi
 * 
 */
@Optimized
@UEditor
// 
public class SimpleSprite extends Plain2D implements IDrawing, ILayerID,
		Serializable {

	// -----------------------------------------------------------------
	// --------------------VARIABLES------------------------------------
	// -----------------------------------------------------------------

	private static final long serialVersionUID = 833416315621548814L;

	// TODO: comment
	/** animo of sprite */
	public Animo animo;

	/** current image of sprite */
	public ITexture image;

	/** Depth of sprite */
	private int layer; // private

	/**
	 * the drawing sheet
	 */
	private int idLayer = -1;

	/**
	 * drawing sheet
	 */
	// private int idLayer;
	/** indicates if draw or not the sprite on screen */
	public boolean visible = true;

	// /** indicates if the sprite is destroyed or not */
	// public boolean destroyed = false;

	/**
	 * indicates if the sprite is fixed or not <br>
	 * it's usually set to true to create the HUD
	 */
	public boolean fixedOnScreen = false;
        
        /**
         * indicates the current state of the Object
         */
        protected StateObject state;
	
	// -----------------------------------------------------------------
	// ---------------------CONSTRUCTORS--------------------------------
	// -----------------------------------------------------------------

	public SimpleSprite(SimpleSprite spr) {
		super(spr);
		this.animo = spr.animo;
		// setImage(spr.image);
		this.image = spr.image;
		if (!quad && autoAddToLists) {
			setIdLayer(spr.idLayer);
			if (spr.layer != 0)
				setLayer(spr.layer);
		}
		this.visible = spr.visible;
		setFixedOnScreen(spr.fixedOnScreen);
	}

	/**
	 * create a new instance of sprite
	 */
	public SimpleSprite() {
		this(0, 0, null, 0, false);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param layer
	 *            the depth of sprite
	 */
	public SimpleSprite(int layer) {
		this(0, 0, null, layer, false);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordinate
	 */
	public SimpleSprite(float x, float y) {
		this(x, y, null, 0, false);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordiante
	 * @param img
	 *            the image of sprite
	 */
	@Creator("x,y,image")
	// 
	public SimpleSprite(@X
	@Info("x")
	float x, @Y
	@Info("x")
	float y, @Info("image")
	ITexture img) {
		this(x, y, img, 0, false);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordinate
	 * @param layer
	 *            the depth of sprite
	 */
	public SimpleSprite(float x, float y, int layer) {
		this(x, y, null, layer, false);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param img
	 *            the image of sprite
	 */
	public SimpleSprite(ITexture img) {
		this(0, 0, img, 0, false);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param img
	 *            the image of sprite
	 * @param layer
	 *            layer of depth of sprite
	 */
	public SimpleSprite(ITexture img, int layer) {
		this(0, 0, img, layer, false);
	}

	// ---

	/**
	 * create a new instance of sprite
	 * 
	 * @param autoAdd
	 *            if it's 'true' the object will be aut-added to the game
	 *            engine's lists.
	 * @param layer
	 *            the depth of sprite
	 * @param idLayer
	 *            the id of Drawing-layer (drawing sheet)
	 */
	public SimpleSprite(boolean autoAdd, int layer, int idLayer) {
		this(autoAdd, 0, 0, null, layer, idLayer);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param autoAdd
	 *            if it's 'true' the object will be aut-added to the game
	 *            engine's lists.
	 * @param layer
	 *            the depth of sprite
	 */
	public SimpleSprite(boolean autoAdd, int layer) {
		this(autoAdd, 0, 0, null, layer, 3);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param autoAdd
	 *            if it's 'true' the object will be aut-added to the game
	 *            engine's lists.
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordinate
	 */
	public SimpleSprite(boolean autoAdd, float x, float y) {
		this(autoAdd, x, y, null, 0, 3);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param autoAdd
	 *            if it's 'true' the object will be aut-added to the game
	 *            engine's lists.
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordiante
	 * @param img
	 *            the image of sprite
	 */
	public SimpleSprite(boolean autoAdd, float x, float y, ITexture img) {
		this(autoAdd, x, y, img, 0, 3);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param autoAdd
	 *            if it's 'true' the object will be aut-added to the game
	 *            engine's lists.
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordiante
	 * @param layer
	 *            the depth of sprite
	 */
	public SimpleSprite(boolean autoAdd, float x, float y, int layer) {
		this(autoAdd, x, y, null, layer, 3);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param autoAdd
	 *            if it's 'true' the object will be aut-added to the game
	 *            engine's lists.
	 * @param img
	 *            the image of sprite
	 */
	public SimpleSprite(boolean autoAdd, ITexture img) {
		this(autoAdd, 0, 0, img, 0, 3);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param autoAdd
	 *            if it's 'true' the object will be aut-added to the game
	 *            engine's lists.
	 * @param img
	 *            the image of sprite
	 * @param layer
	 *            the depth of sprite
	 */
	public SimpleSprite(boolean autoAdd, ITexture img, int layer) {
		this(autoAdd, 0, 0, img, layer, 3);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param layer
	 *            the depth of sprite
	 * @param idLayer
	 *            the id of Drawing-layer (drawing sheet)
	 */
	public SimpleSprite(int layer, int idLayer) {
		this(true, 0, 0, null, layer, idLayer);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordiante
	 * @param layer
	 *            the depth of sprite
	 * @param idLayer
	 *            the id of Drawing-layer (drawing sheet)
	 */
	public SimpleSprite(float x, float y, int layer, int idLayer) {
		this(true, x, y, null, layer, idLayer);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordiante
	 * @param img
	 *            the image of sprite
	 * @param layer
	 *            the depth of sprite
	 * @param idLayer
	 *            the id of Drawing-layer (drawing sheet)
	 */
	@Creator("x, y, imgage,layer, layerid")
	// 
	public SimpleSprite(@X
	@Info("x")
	float x, @Y
	@Info("y")
	float y, ITexture img, @Info("layer")
	int layer, @Info("id-layer")
	int idLayer) {
		this(true, x, y, img, layer, idLayer);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param autoAdd
	 *            if it's 'true' the object will be aut-added to the game
	 *            engine's lists.
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordiante
	 * @param layer
	 *            the depth of sprite
	 * @param idLayer
	 *            the id of Drawing-layer (drawing sheet)
	 */
	public SimpleSprite(boolean autoAdd, float x, float y, int layer,
			int idLayer) {
		this(true, x, y, null, layer, idLayer);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param img
	 *            the image of sprite
	 * @param layer
	 *            the depth of sprite
	 * @param idLayer
	 *            the id of Drawing-layer (drawing sheet)
	 */
	public SimpleSprite(ITexture img, int layer, int idLayer) {
		this(true, 0, 0, img, layer, idLayer);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param autoAdd
	 *            if it's 'true' the object will be aut-added to the game
	 *            engine's lists.
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordiante
	 * @param img
	 *            the image of sprite
	 * @param layer
	 *            the depth of sprite
	 * @param idLayer
	 *            the id of Drawing-layer (drawing sheet)
	 */
	public SimpleSprite(boolean autoAdd, float x, float y, ITexture img,
			int layer, int idLayer) {
		super(autoAdd, x, y, 0, 0);
		// StaticRef.layers[idLayer].add(this);
		if (autoAdd)
			setIdLayer(idLayer);
		// setXY(x,y);
		// this.layer = layer;
		if (layer != 0)
			setLayer(layer);
		setImage(img);
		type.set("$_SIMPLE-SPRITE");
	}

	// quads

	/**
	 * create a new instance of sprite
	 * 
	 * @param quad
	 *            if the 'quad' is 'true' the object will be use the
	 *            quad-manager.<br>
	 *            Usually the quad-manager is disallowed<br>
	 */

	public SimpleSprite(boolean quad) {
		this(0, 0, null, 0, quad);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param layer
	 *            the depth of sprite
	 * @param quad
	 *            if the 'quad' is 'true' the object will be use the
	 *            quad-manager.<br>
	 *            Usually the quad-manager is disallowed<br>
	 */

	public SimpleSprite(int layer, boolean quad) {
		this(0, 0, null, layer, quad);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordiante
	 * @param quad
	 *            if the 'quad' is 'true' the object will be use the
	 *            quad-manager.<br>
	 *            Usually the quad-manager is disallowed<br>
	 */

	public SimpleSprite(float x, float y, boolean quad) {
		this(x, y, null, 0, quad);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordiante
	 * @param img
	 *            the image of sprite
	 * @param quad
	 *            if the 'quad' is 'true' the object will be use the
	 *            quad-manager.<br>
	 *            Usually the quad-manager is disallowed<br>
	 */

	public SimpleSprite(float x, float y, ITexture img, boolean quad) {
		this(x, y, img, 0, quad);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordiante
	 * @param layer
	 *            the depth of sprite
	 * @param quad
	 *            if the 'quad' is 'true' the object will be use the
	 *            quad-manager.<br>
	 *            Usually the quad-manager is disallowed<br>
	 */

	public SimpleSprite(float x, float y, int layer, boolean quad) {
		this(x, y, null, layer, quad);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param img
	 *            the image of sprite
	 * @param quad
	 *            if the 'quad' is 'true' the object will be use the
	 *            quad-manager.<br>
	 *            Usually the quad-manager is disallowed<br>
	 */

	public SimpleSprite(ITexture img, boolean quad) {
		this(0, 0, img, 0, quad);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param img
	 *            the image of sprite
	 * @param layer
	 *            the depth of sprite
	 * @param quad
	 *            if the 'quad' is 'true' the object will be use the
	 *            quad-manager.<br>
	 *            Usually the quad-manager is disallowed<br>
	 */

	public SimpleSprite(ITexture img, int layer, boolean quad) {
		this(0, 0, img, layer, quad);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordiante
	 * @param img
	 *            the image of sprite
	 * @param layer
	 *            the depth of sprite
	 */

	public SimpleSprite(float x, float y, ITexture img, int layer) {
		this(x, y, img, layer, false);
	}

	/**
	 * create a new instance of sprite
	 * 
	 * @param x
	 *            x cartesian coordinate
	 * @param y
	 *            y cartesian coordiante
	 * @param img
	 *            the image of sprite
	 * @param layer
	 *            the depth of sprite
	 * @param quad
	 *            if the 'quad' is 'true' the object will be use the
	 *            quad-manager.<br>
	 *            Usually the quad-manager is disallowed<br>
	 */
	@Creator("x, y, imgage, layer, layerid, quad")
	// 
	public SimpleSprite(@X
	@Info("x")
	float x, @Y
	@Info("y")
	float y, @Info("image")
	ITexture img, @Info("layer")
	int layer, @Info("use quad")
	boolean quad) {
		super(x, y, 0, 0, quad);
		// setXY(x,y);
		// this.layer = layer;
		// StaticRef.layers[ILayerID.defaultIdLayer].add(this);
		if (!quad && autoAddToLists) {
			setIdLayer(ILayerID.defaultIdLayer);
			if (layer != 0)
				setLayer(layer);
		}
		this.image = img;
		if (image != null)
			setSize(image.getWidth(), image.getHeight());
		type.set("$_SIMPLE-SPRITE");
	}

	// -----------------------------------------------------------------
	// ---------------------DRAWING-------------------------------------
	// -----------------------------------------------------------------

	public void render() {
		if (animo != null) {
			image = animo.get();// .getCurrentImage();
		}
		if (image == null)
			return;
		if (!visible)
			return;
		if (width <= 0 || height <= 0)
			return;
		Camera camera = StaticRef.getCamera();
		float oldZoom = camera.getZoom2D();
		int nx = (int) (x - StaticRef.getCamera().x);
		int ny = (int) (y - StaticRef.getCamera().y);
		// TODO
		// if (GL11.glIsEnabled(GL11.GL_COLOR_MATERIAL))
		// GL11.glDisable(GL11.GL_COLOR_MATERIAL);

		GL11.glColor4f(1, 1, 1, 1);

		if (fixedOnScreen
				|| (nx < camera.getWidth() || ny < camera.getHeight()) && !((nx + width) < 0 || (ny + height) < 0)) {

			float translatex = nx, translatey = ny;
			if (fixedOnScreen) {
				translatex = (float) x;
				translatey = (float) y;
				camera.zoom2D(1.0f);
			} 
			// image.bind();
			//
			// drawPlain(translatex, translatey);

			if (image.getAlphaMask() != null) {
				OpenGLState.enableBlending();
				GL11.glBlendFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO);
				image.getAlphaMask().bind();
				drawPlain(translatex, translatey);
				GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
				image.bind();
				drawPlain(translatex, translatey);
			} else {
				OpenGLState.disableBlending();
				image.bind();
				drawPlain(translatex, translatey);
			}
			if (fixedOnScreen)
				camera.zoom2D(oldZoom);
		}
		camera.objectsAtScreen.add(this);
	}

	/** draw a dinamic plain */
	protected void drawPlain(float x, float y) {
		GL11.glBegin(GL11.GL_QUADS); // Draw A Quad
		GL11.glTexCoord2f(image.getXStart(), image.getYStart());
		GL11.glVertex2f(x, y);
		GL11.glTexCoord2f(image.getXEnd(), image.getYStart());
		GL11.glVertex2f(x + width, y);
		GL11.glTexCoord2f(image.getXEnd(), image.getYEnd());
		GL11.glVertex2f(x + width, y + height);
		GL11.glTexCoord2f(image.getXStart(), image.getYEnd());
		GL11.glVertex2f(x, y + height);
		GL11.glEnd();
	}

	// -----------------------------------------------------------------
	// ----------------------USER METHODS-------------------------------
	// -----------------------------------------------------------------

	/** sets the depth */
	@Method("set Layer")
	//
	public void setLayer(@Info("layer")
	int layer) {
		this.layer = layer;
		readdToDrawingLists();
	}

	public int getLayer() {
		return layer;
	}
        
        public void setState(StateObject state) {
            this.state = state;
            if (state != null) {
                state.setObject(this);
            }
        }
        
        public StateObject getState() {
            return state;
        }
        
        public void loopState() {
            if (state != null) {
                state.loop();
            }
        }

	// -----------------------------------------------------------------
	// ----------------------- DESTROY -------------------------------
	// -----------------------------------------------------------------

	public void destroy() {
		if (isDestroyed())
			return;
		super.destroy();
		visible = false;
		image = null;
		animo = null;
	}

	// public boolean isDestroyed() {
	// return destroyed;
	// }

	// public void onDestroy() {
	// }

	// -----------------------------------------------------------------
	// ---------------------------- PLAIN ------------------------------
	// -----------------------------------------------------------------

	/**
	 * returns the x coordinate relative to WORLD
	 * 
	 */
	public float getX() {
		if (fixedOnScreen) {
			return x + StaticRef.getCamera().getx();
		} else {
			return x;
		}
	}

	/**
	 * returns the y coordinate relative to WORLD
	 * 
	 */
	public float getY() {
		if (fixedOnScreen) {
			return y + StaticRef.getCamera().gety();
		} else {
			return y;
		}
	}

	/**
	 * returns the x coordinate relative to SCREEN
	 * 
	 */
	public float getXOnScreen() {
		if (fixedOnScreen) {
			return x;
		} else {
			return (x - StaticRef.getCamera().getx());
		}
	}

	/**
	 * returns the y coordinate relative to SCREEN
	 * 
	 */
	public float getYOnScreen() {
		if (fixedOnScreen) {
			return y;
		} else {
			return (y - StaticRef.getCamera().gety());
		}
	}

	// -----------------------------------------------------------------
	// ---------------------------- IDLAYER-----------------------------
	// -----------------------------------------------------------------

	public int getIdLayer() {
		return idLayer;
	}

	@Method("set Id Layer")
	//
	public void setIdLayer(@Info("id layer")
	int id) {
		if (idLayer != -1) {
			StaticRef.layers[idLayer].remove(this);
		}
		if (id < 0) {
			id = 0;
		} else if (id > StaticRef.layers.length) {
			id = StaticRef.layers.length - 1;
		}
		idLayer = id;
		StaticRef.layers[idLayer].add(this);
	}

	/**
	 * gets the animo associated to the object
	 * 
	 * @return the animo
	 */
	public Animo getAnimo() {
		return animo;
	}

	/**
	 * sets the animo associated to the object
	 * 
	 * @param animo
	 *            the animo to assign
	 */
	// TODO: @Method("set Animo") //
	public void setAnimo(Animo animo) {
		this.animo = animo;
	}

	/**
	 * sets if the object is fixed or not to screen<br>
	 * a fixed object have the x,y coordinates relatives to the monitor: not to
	 * the world's coordinates
	 * 
	 * @param fixedOnScreen
	 */
	@Method("set fixed on screen")
	//
	public void setFixedOnScreen(@Info("fixed on screen")
	boolean fixedOnScreen) {
		this.fixedOnScreen = fixedOnScreen;
	}

	/**
	 * returns if the object is fixed or not to screen<br>
	 * a fixed object have the x,y coordinates relatives to the monitor: not to
	 * the world's coordinates
	 */
	public boolean isFixedOnScreen() {
		return fixedOnScreen;
	}
	
	/**
	 * returns the current image of the object
	 * 
	 * @return
	 */
	public ITexture getImage() {
		return image;
	}

	/**
	 * changes the current image of the object
	 * 
	 * @param image
	 */
	@Method("change image")
	//
	public void setImage(@Info("image")
	ITexture image) {
		if (image != null) {
			if (this.image == null || width == 0 || height == 0) {
				width = image.getWidth();
				height = image.getHeight();
			}
		}
		this.image = image;
	}

	public Object clone() {
		return new SimpleSprite(this);
	}

}
